#  S32K144EVB_Can_Bootloader.py
#
#  ------------------------------------------------------------------
#  Author : caiyingming   
#  Last change: 12.2.2023
#
#  Language: Python 3.6.6
#  ------------------------------------------------------------------
#
from zlgcan import *
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tkinter import messagebox
import threading
import time
import json
import logging

GRPBOX_WIDTH    = 200
MSGCNT_WIDTH    = 50
MSGID_WIDTH     = 80
MSGDIR_WIDTH    = 60
MSGINFO_WIDTH   = 100
MSGLEN_WIDTH    = 60
MSGDATA_WIDTH   = 200
MSGVIEW_WIDTH   = MSGCNT_WIDTH + MSGID_WIDTH + MSGDIR_WIDTH + MSGINFO_WIDTH + MSGLEN_WIDTH + MSGDATA_WIDTH
MSGVIEW_HEIGHT  = 500
SENDVIEW_HEIGHT = 125

WIDGHT_WIDTH    = GRPBOX_WIDTH + MSGVIEW_WIDTH + 40
WIDGHT_HEIGHT   = MSGVIEW_HEIGHT + SENDVIEW_HEIGHT + 20

MAX_DISPLAY     = 1000
MAX_RCV_NUM     = 10

USBCANFD_TYPE    = (41, 42, 43)
USBCAN_XE_U_TYPE = (20, 21, 31)
USBCAN_I_II_TYPE = (3, 4)

# for user define
DOWN_ID            = 0x64
UP_ID_Str          = 'c8'
DOWN_LINK_CMD      = 0xFF
DOWN_LINE_END_CMD  = 0xFE
DOWN_FILE_END_CMD  = 0xFD
UP_READY_Str       = 'c3 '
UP_READY_STATUS    = 0x03
UP_BUSY_Str        = 'c2 '
UP_BUSY_STATUS     = 0x02
UP_PRGEND_Str      = 'C1 '
UP_PRGEND_STATUS   = 0x01
UP_ERR_Str         = '00 '
UP_ERR_STATUS      = 0x00

# select use thread for receiving can frame or not
USE_THREAD         = False

###############################################################################
class ZCAN_Demo(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("S32K144EVB_Can_Bootloader_PC")
        self.resizable(False, False)
        self.geometry(str(WIDGHT_WIDTH) + "x" + str(WIDGHT_HEIGHT) + '+200+100')
        self.protocol("WM_DELETE_WINDOW", self.Form_OnClosing)
        logging.basicConfig(filename='log.txt', filemode='w', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s - %(funcName)s', level=logging.DEBUG)
        #logging.disable(logging.DEBUG)

        self.DeviceInit()
        self.WidgetsInit()

        self._dev_info = None
        with open("./dev_info.json", "r") as fd:
            self._dev_info = json.load(fd)
        if self._dev_info == None:
            print("device info no exist!")
            return 

        self.DeviceInfoInit()
        self.ChnInfoUpdate(self._isOpen)

    def DeviceInit(self):
        self._zcan       = ZCAN() 
        self._dev_handle = INVALID_DEVICE_HANDLE 
        self._can_handle = INVALID_CHANNEL_HANDLE 

        self._isOpen = False
        self._isChnOpen = False

        #current device info
        self._is_canfd = False
        self._res_support = False

        #Transmit and receive count display
        self._tx_cnt = 0
        self._rx_cnt = 0
        self._view_cnt = 0

        #read can/canfd message thread
        self._read_thread = None
        self._terminated = False

        #period send var
        self._is_sending   = False
        self._id_increase  = False 
        self._send_num     = 1
        self._send_cnt     = 1
        self._is_canfd_msg = False
        self._send_msgs    = None
        self._send_thread  = None

        #mcu status
        self.mcu_Status = UP_ERR_STATUS

    def WidgetsInit(self):
        self._dev_frame = tk.Frame(self)
        self._dev_frame.grid(row=0, column=0, padx=2, pady=2, sticky=tk.NSEW)

        # Device connect group
        self.gbDevConnect = tk.LabelFrame(self._dev_frame, height=100, width=GRPBOX_WIDTH, text="设备选择")
        self.gbDevConnect.grid_propagate(0)
        self.gbDevConnect.grid(row=0, column=0, padx=2, pady=2, sticky=tk.NE)
        self.DevConnectWidgetsInit()

        self.gbCANCfg = tk.LabelFrame(self._dev_frame, height=170, width=GRPBOX_WIDTH, text="通道配置")
        self.gbCANCfg.grid(row=1, column=0, padx=2, pady=2, sticky=tk.NSEW)
        self.gbCANCfg.grid_propagate(0)
        self.CANChnWidgetsInit()

        self.gbDevInfo = tk.LabelFrame(self._dev_frame, height=230, width=GRPBOX_WIDTH, text="设备信息")
        self.gbDevInfo.grid(row=2, column=0, padx=2, pady=2, sticky=tk.NSEW)
        self.gbDevInfo.grid_propagate(0)
        self.DevInfoWidgetsInit()

        self.gbMsgDisplay = tk.LabelFrame(height=MSGVIEW_HEIGHT, width=MSGVIEW_WIDTH + 12, text="Status of Data Sent")
        self.gbMsgDisplay.grid(row=0, column=1, padx=2, pady=2, sticky=tk.NSEW)
        self.gbMsgDisplay.grid_propagate(0)
        self.StatusDisplayWidgetsInit()

        self.gbMsgSend = tk.LabelFrame(heigh=SENDVIEW_HEIGHT, width=MSGVIEW_WIDTH + 12, text="ECU Flashing")
        self.gbMsgSend.grid(row=2, column=1, padx=2, pady=2, sticky=tk.NSEW)
        self.gbMsgSend.grid_propagate(0)
        
        # File send group
        self.FileSendWidgetsInit()

    def DeviceInfoInit(self):
        self.cmbDevType["value"] = tuple([dev_name for dev_name in self._dev_info])
        self.cmbDevType.current(0)

    def DevConnectWidgetsInit(self):
        #Device Type
        tk.Label(self.gbDevConnect, text="设备类型:").grid(row=0, column=0, sticky=tk.E)
        self.cmbDevType = ttk.Combobox(self.gbDevConnect, width=16, state="readonly")
        self.cmbDevType.grid(row=0, column=1, sticky=tk.E)

        #Device Index
        tk.Label(self.gbDevConnect, text="设备索引:").grid(row=1, column=0, sticky=tk.E)
        self.cmbDevIdx = ttk.Combobox(self.gbDevConnect, width=16, state="readonly")
        self.cmbDevIdx.grid(row=1, column=1, sticky=tk.E)
        self.cmbDevIdx["value"] = tuple([i for i in range(4)])
        self.cmbDevIdx.current(0)

        #Open/Close Device
        self.strvDevCtrl = tk.StringVar()
        self.strvDevCtrl.set("打开")
        self.btnDevCtrl = ttk.Button(self.gbDevConnect, textvariable=self.strvDevCtrl, command=self.BtnOpenDev_Click)
        self.btnDevCtrl.grid(row=2, column=0, columnspan=2, pady=2) 

    def CANChnWidgetsInit(self):
        #CAN Channel
        tk.Label(self.gbCANCfg, anchor=tk.W, text="CAN通道:").grid(row=0, column=0, sticky=tk.W)
        self.cmbCANChn = ttk.Combobox(self.gbCANCfg, width=12, state="readonly")
        self.cmbCANChn.grid(row=0, column=1, sticky=tk.E)

        #Work Mode
        tk.Label(self.gbCANCfg, anchor=tk.W, text="工作模式:").grid(row=1, column=0, sticky=tk.W)
        self.cmbCANMode = ttk.Combobox(self.gbCANCfg, width=12, state="readonly")
        self.cmbCANMode.grid(row=1, column=1, sticky=tk.E)

        #CAN Baudrate 
        tk.Label(self.gbCANCfg, anchor=tk.W, text="波特率:").grid(row=2, column=0, sticky=tk.W)
        self.cmbBaudrate = ttk.Combobox(self.gbCANCfg, width=12, state="readonly")
        self.cmbBaudrate.grid(row=2, column=1, sticky=tk.W)
        
        #CAN Data Baudrate 
        tk.Label(self.gbCANCfg, anchor=tk.W, text="数据域波特率:").grid(row=3, column=0, sticky=tk.W)
        self.cmbDataBaudrate = ttk.Combobox(self.gbCANCfg, width=12, state="readonly")
        self.cmbDataBaudrate.grid(row=3, column=1, sticky=tk.W)

        #resistance enable
        tk.Label(self.gbCANCfg, anchor=tk.W, text="终端电阻:").grid(row=4, column=0, sticky=tk.W)
        self.cmbResEnable = ttk.Combobox(self.gbCANCfg, width=12, state="readonly")
        self.cmbResEnable.grid(row=4, column=1, sticky=tk.W)

        #CAN Control
        self.strvCANCtrl = tk.StringVar()
        self.strvCANCtrl.set("打开")
        self.btnCANCtrl = ttk.Button(self.gbCANCfg, textvariable=self.strvCANCtrl, command=self.BtnOpenCAN_Click) 
        self.btnCANCtrl.grid(row=5, column=0, columnspan=2, padx=2, pady=2)

    def DevInfoWidgetsInit(self):
        #Hardware Version
        tk.Label(self.gbDevInfo, anchor=tk.W, text="硬件版本:").grid(row=0, column=0, sticky=tk.W)
        self.strvHwVer = tk.StringVar(value='')
        tk.Label(self.gbDevInfo, anchor=tk.W, textvariable=self.strvHwVer).grid(row=0, column=1, sticky=tk.W)

        #Firmware Version
        tk.Label(self.gbDevInfo, anchor=tk.W, text="固件版本:").grid(row=1, column=0, sticky=tk.W)
        self.strvFwVer = tk.StringVar(value='')
        tk.Label(self.gbDevInfo, anchor=tk.W, textvariable=self.strvFwVer).grid(row=1, column=1, sticky=tk.W)

        #Driver Version
        tk.Label(self.gbDevInfo, anchor=tk.W, text="驱动版本:").grid(row=2, column=0, sticky=tk.W)
        self.strvDrVer = tk.StringVar(value='')
        tk.Label(self.gbDevInfo, anchor=tk.W, textvariable=self.strvDrVer).grid(row=2, column=1, sticky=tk.W)

        #Interface Version
        tk.Label(self.gbDevInfo, anchor=tk.W, text="动态库版本:").grid(row=3, column=0, sticky=tk.W)
        self.strvInVer = tk.StringVar(value='')
        tk.Label(self.gbDevInfo, anchor=tk.W, textvariable=self.strvInVer).grid(row=3, column=1, sticky=tk.W)

        #CAN num
        tk.Label(self.gbDevInfo, anchor=tk.W, text="CAN通道数:").grid(row=4, column=0, sticky=tk.W)
        self.strvCANNum = tk.StringVar(value='')
        tk.Label(self.gbDevInfo, anchor=tk.W, textvariable=self.strvCANNum).grid(row=4, column=1, sticky=tk.W)

        #Device Serial
        tk.Label(self.gbDevInfo, anchor=tk.W, text="设备序列号:").grid(row=5, column=0, sticky=tk.W)
        self.strvSerial = tk.StringVar(value='')
        tk.Label(self.gbDevInfo, anchor=tk.W, textvariable=self.strvSerial).grid(row=6, column=0, columnspan=2, sticky=tk.W)
    
        #Hardware type
        tk.Label(self.gbDevInfo, anchor=tk.W, text="硬件类型:").grid(row=7, column=0, sticky=tk.W)
        self.strvHwType = tk.StringVar(value='')
        tk.Label(self.gbDevInfo, anchor=tk.W, textvariable=self.strvHwType).grid(row=8, column=0, columnspan=2, sticky=tk.W)
    
    def StatusDisplayWidgetsInit(self):
        self._msg_frame = tk.Frame(self.gbMsgDisplay, height=MSGVIEW_HEIGHT, width=WIDGHT_WIDTH-GRPBOX_WIDTH+10)
        self._msg_frame.pack(side=tk.TOP)

        frameAppFile = tk.Frame(self._msg_frame, height=20)
        frameAppFile.pack(side=tk.LEFT)
        scrollbarAppFile = tk.Scrollbar(frameAppFile)
        scrollbarAppFile.pack(side = tk.RIGHT, fill = tk.Y)
        self.OutputText = tk.Text(frameAppFile, wrap = tk.WORD, width = 80, height = 35, yscrollcommand = scrollbarAppFile.set)
        self.OutputText.pack()
        scrollbarAppFile.config(command = self.OutputText.yview)

        self.btnClrCnt = ttk.Button(self.gbMsgDisplay, width=10, text="清空", command=self.BtnClrCnt_Click) 
        self.btnClrCnt.pack(side=tk.RIGHT)

        self.strvRxCnt = tk.StringVar()
        self.strvRxCnt.set("0")
        tk.Label(self.gbMsgDisplay, anchor=tk.W, width=5, textvariable=self.strvRxCnt).pack(side=tk.RIGHT)
        tk.Label(self.gbMsgDisplay, width=10, text="接收帧数:").pack(sid=tk.RIGHT)

        self.strvTxCnt = tk.StringVar()
        self.strvTxCnt.set("0")
        tk.Label(self.gbMsgDisplay, anchor=tk.W, width=5, textvariable=self.strvTxCnt).pack(side=tk.RIGHT)
        tk.Label(self.gbMsgDisplay, width=10, text="发送帧数:").pack(side=tk.RIGHT)

    def FileSendWidgetsInit(self):
        #Send Type
        self.buttonSelect = tk.Button(self.gbMsgSend, text="Select Files", relief="raised", command=self.processButtonSelect)
        self.buttonSelect.grid(row = 0, column = 1, padx = 5, pady = 3)
        self.labelFile = tk.Label(self.gbMsgSend, anchor = "w", relief="sunken", width = 30, wraplength = 200, justify = "left")
        self.labelFile.grid(row = 0, column = 2, columnspan = 2, padx = 5, pady = 3, sticky = tk.W)
        self.fileName = ""
        
        self.strvSend = tk.StringVar()
        self.strvSend.set("Start")
        self.buttonSS = ttk.Button(self.gbMsgSend, textvariable=self.strvSend, command = self.processButtonSS)
        self.buttonSS.grid(row = 0, column = 4, padx = 5, pady = 3, sticky = tk.E)
        self.buttonSS["state"] = tk.DISABLED

        self.progressbarSend=ttk.Progressbar(self.gbMsgSend)
        self.progressbarSend.grid(row = 1, column = 1)
        self.progressbarSend['length']=200

###############################################################################
### Function 
###############################################################################
    def __dlc2len(self, dlc):
        if dlc <= 8:
            return dlc
        elif dlc == 9:
            return 12
        elif dlc == 10:
            return 16
        elif dlc == 11:
            return 20
        elif dlc == 12:
            return 24
        elif dlc == 13:
            return 32
        elif dlc == 14:
            return 48
        else:
            return 64

    def CANMsg2View(self, msg, is_transmit=True):
        view = []
        view.append(str(self._view_cnt))
        self._view_cnt += 1 
        view.append(hex(msg.can_id)[2:])
        view.append("发送" if is_transmit else "接收")

        str_info = ''
        str_info += 'EXT' if msg.eff else 'STD'
        if msg.rtr:
            str_info += ' RTR'
        view.append(str_info)
        view.append(str(msg.can_dlc))
        if msg.rtr:
            view.append('')
        else:
            view.append(''.join(hex(msg.data[i])[2:] + ' ' for i in range(msg.can_dlc)))
        return view

    def CANFDMsg2View(self, msg, is_transmit=True):
        view = [] 
        view.append(str(self._view_cnt))
        self._view_cnt += 1 
        
        view.append(hex(msg.can_id)[2:])
        view.append("发送" if is_transmit else "接收")

        str_info = ''
        str_info += 'EXT' if msg.eff else 'STD'
        if msg.rtr:
            str_info += ' RTR'
        else:
            str_info += ' FD'
            if msg.brs:
                str_info += ' BRS'
            if msg.esi:
                str_info += ' ESI' 
        view.append(str_info)
        view.append(str(msg.len))
        if msg.rtr:
            view.append('')
        else:
            view.append(''.join(hex(msg.data[i])[2:] + ' ' for i in range(msg.len)))
        return view

    def ChnInfoUpdate(self, is_open):
        #通道信息获取
        cur_dev_info = self._dev_info[self.cmbDevType.get()]
        cur_chn_info = cur_dev_info["chn_info"]
        
        if is_open:
            # 通道 
            self.cmbCANChn["value"] = tuple([i for i in range(cur_dev_info["chn_num"])])
            self.cmbCANChn.current(0)

            # 工作模式
            self.cmbCANMode["value"] = ("正常模式", "只听模式")
            self.cmbCANMode.current(0)

            # 波特率
            self.cmbBaudrate["value"] = tuple([brt for brt in cur_chn_info["baudrate"].keys()])
            self.cmbBaudrate.current(len(self.cmbBaudrate["value"]) - 1)

            if cur_chn_info["is_canfd"] == True:
                # 数据域波特率 
                self.cmbDataBaudrate["value"] = tuple([brt for brt in cur_chn_info["data_baudrate"].keys()])
                self.cmbDataBaudrate.current(0)
                self.cmbDataBaudrate["state"] = "readonly"

            if cur_chn_info["sf_res"] == True:
                self.cmbResEnable["value"] = ("使能", "失能")
                self.cmbResEnable.current(0)
                self.cmbResEnable["state"] = "readonly"

            self.btnCANCtrl["state"] = tk.NORMAL
        else:
            self.cmbCANChn["state"] = tk.DISABLED
            self.cmbCANMode["state"] = tk.DISABLED
            self.cmbBaudrate["state"] = tk.DISABLED
            self.cmbDataBaudrate["state"] = tk.DISABLED
            self.cmbResEnable["state"] = tk.DISABLED
            
            self.cmbCANChn["value"] = ()
            self.cmbCANMode["value"] = ()
            self.cmbBaudrate["value"] = ()
            self.cmbDataBaudrate["value"] = ()
            self.cmbResEnable["value"] = ()

            self.btnCANCtrl["state"] = tk.DISABLED

    def ChnInfoDisplay(self, enable):
        if enable:
            self.cmbCANChn["state"] = "readonly"
            self.cmbCANMode["state"] = "readonly"
            self.cmbBaudrate["state"] = "readonly" 
            if self._is_canfd: 
                self.cmbDataBaudrate["state"] = "readonly" 
            if self._res_support: 
                self.cmbResEnable["state"] = "readonly"
        else:
            self.cmbCANChn["state"] = tk.DISABLED
            self.cmbCANMode["state"] = tk.DISABLED
            self.cmbBaudrate["state"] = tk.DISABLED
            self.cmbDataBaudrate["state"] = tk.DISABLED
            self.cmbResEnable["state"] = tk.DISABLED
    
    def MsgReadThreadFunc(self):
        try:
            while not self._terminated:
                if ((self._is_sending) and (self.fileName != "")):
                    logging.debug('in reading data thread')
                    can_num = self._zcan.GetReceiveNum(self._can_handle, ZCAN_TYPE_CAN)
                    canfd_num = self._zcan.GetReceiveNum(self._can_handle, ZCAN_TYPE_CANFD)

                    if can_num:
                        while can_num and not self._terminated:
                            read_cnt = MAX_RCV_NUM if can_num >= MAX_RCV_NUM else can_num
                            can_msgs, act_num = self._zcan.Receive(self._can_handle, read_cnt, MAX_RCV_NUM)
                            if act_num: 
                                #update data
                                self._rx_cnt += act_num 
                                self.strvRxCnt.set(str(self._rx_cnt))
                                for i in range(act_num):
                                    value = self.CANMsg2View(can_msgs[i].frame, False)
                                    if value[1] == UP_ID_Str:
                                        if value[5] == UP_READY_Str:
                                            self.mcu_Status = UP_READY_STATUS
                                            logging.debug('No %d frame,status of mcu is ready',i)
                                        elif value[5] == UP_BUSY_Str:
                                            self.mcu_Status = UP_BUSY_STATUS
                                            logging.debug('No %d frame,status of mcu is busy',i)
                                        elif value[5] == UP_PRGEND_Str:
                                            self.mcu_Status = UP_PRGEND_STATUS
                                            logging.debug('No %d frame,status of mcu is progarm',i)
                                        elif value[5] == UP_ERR_Str:
                                            self.mcu_Status = UP_ERR_STATUS
                                            logging.debug('No %d frame,status of mcu is init',i)
                    
                            else:
                                break
                            can_num -= act_num
                    if canfd_num:
                        while canfd_num and not self._terminated:
                            read_cnt = MAX_RCV_NUM if canfd_num >= MAX_RCV_NUM else canfd_num
                            canfd_msgs, act_num = self._zcan.ReceiveFD(self._can_handle, read_cnt, MAX_RCV_NUM)
                            if act_num: 
                                #update data
                                self._rx_cnt += act_num 
                                self.strvRxCnt.set(str(self._rx_cnt))
                                for i in range(act_num):
                                    value = self.CANFDMsg2View(canfd_msgs[i].frame, False)
                                    logging.debug(value)
                            else:
                                break
                            canfd_num -= act_num
        except:
            self.OutputText.insert(tk.END,"Stop reading the file !\r\n") 

    def MsgReadFunc(self):
            if not self._terminated:
                can_num = self._zcan.GetReceiveNum(self._can_handle, ZCAN_TYPE_CAN)
                canfd_num = self._zcan.GetReceiveNum(self._can_handle, ZCAN_TYPE_CANFD)
                logging.debug('In reading data')

                if can_num:
                    while can_num and not self._terminated:
                        read_cnt = MAX_RCV_NUM if can_num >= MAX_RCV_NUM else can_num
                        can_msgs, act_num = self._zcan.Receive(self._can_handle, read_cnt, MAX_RCV_NUM)
                        if act_num: 
                            #update data
                            self._rx_cnt += act_num 
                            self.strvRxCnt.set(str(self._rx_cnt))
                            for i in range(act_num):
                                value = self.CANMsg2View(can_msgs[i].frame, False)
                                if value[1] == UP_ID_Str:
                                    if value[5] == UP_READY_Str:
                                        self.mcu_Status = UP_READY_STATUS
                                        logging.debug('No %d frame,status of mcu is ready',i)
                                    elif value[5] == UP_BUSY_Str:
                                        self.mcu_Status = UP_BUSY_STATUS
                                        logging.debug('No %d frame,status of mcu is busy',i)
                                    elif value[5] == UP_PRGEND_Str:
                                        self.mcu_Status = UP_PRGEND_STATUS
                                        logging.debug('No %d frame,status of mcu is progarm',i)
                                    elif value[5] == UP_ERR_Str:
                                        self.mcu_Status = UP_ERR_STATUS
                                        logging.debug('No %d frame,status of mcu is init',i)
                
                        else:
                            break
                        can_num -= act_num
                if canfd_num:
                    while canfd_num and not self._terminated:
                        read_cnt = MAX_RCV_NUM if canfd_num >= MAX_RCV_NUM else canfd_num
                        canfd_msgs, act_num = self._zcan.ReceiveFD(self._can_handle, read_cnt, MAX_RCV_NUM)
                        if act_num: 
                            #update data
                            self._rx_cnt += act_num 
                            self.strvRxCnt.set(str(self._rx_cnt))
                            for i in range(act_num):
                                value = self.CANFDMsg2View(canfd_msgs[i].frame, False)
                                logging.debug(value)
                        else:
                            break
                        canfd_num -= act_num

    def PeriodSendIdUpdate(self, is_ext):
        self._cur_id += 1
        if is_ext:
            if self._cur_id > 0x1FFFFFFF:
                self._cur_id = 0
        else:
            if self._cur_id > 0x7FF:
                self._cur_id = 0

    def MsgSend(self, msg, is_canfd, num=1, cnt=1, period=0, id_increase=0):
        self._id_increase  = id_increase
        self._send_num     = num if num else 1
        self._send_cnt     = cnt if cnt else 1
        self._is_canfd_msg = is_canfd
        self._is_sending = True

        if self._is_canfd_msg:    
            self._send_msgs = (ZCAN_TransmitFD_Data * self._send_num)()
        else:
            self._send_msgs = (ZCAN_Transmit_Data * self._send_num)()

        self._cur_id = msg.frame.can_id

        for i in range(self._send_num):
            self._send_msgs[i] = msg
            self._send_msgs[i].frame.can_id = self._cur_id
            self.PeriodSendIdUpdate(self._send_msgs[i].frame.eff)

        if self._is_canfd_msg: 
            ret = self._zcan.TransmitFD(self._can_handle, self._send_msgs, self._send_num)
        else:
            ret = self._zcan.Transmit(self._can_handle, self._send_msgs, self._send_num)
            logging.debug('In sending data')

        #update transmit display
        self._tx_cnt += ret
        self.strvTxCnt.set(str(self._tx_cnt))
        
        if ret != self._send_num:
            self.PeriodSendComplete()
            messagebox.showerror(title="发送报文", message="发送失败！")
            return

        self._send_cnt -= 1
        if self._send_cnt:
            if self._id_increase:
                for i in range(self._send_num):
                    self._send_msgs[i].frame.can_id = self._cur_id
                    self.PeriodSendIdUpdate(self._send_msgs[i].frame.eff)

    def DevInfoRead(self):
        info = self._zcan.GetDeviceInf(self._dev_handle)
        if info != None:
            self.strvHwVer.set(info.hw_version)
            self.strvFwVer.set(info.fw_version)
            self.strvDrVer.set(info.dr_version)
            self.strvInVer.set(info.in_version)
            self.strvCANNum.set(str(info.can_num))
            self.strvSerial.set(info.serial)
            self.strvHwType.set(info.hw_type)

    def DevInfoClear(self):
        self.strvHwVer.set('')
        self.strvFwVer.set('')
        self.strvDrVer.set('')
        self.strvInVer.set('')
        self.strvCANNum.set('')
        self.strvSerial.set('')
        self.strvHwType.set('')
###############################################################################
### Event handers
###############################################################################
    def Form_OnClosing(self):
        if self._isOpen:
            self.btnDevCtrl.invoke()

        self.destroy()

    def BtnOpenDev_Click(self):
        if self._isOpen:
            #Close Channel 
            if self._isChnOpen:
                self.btnCANCtrl.invoke()

            #Close Device
            self._zcan.CloseDevice(self._dev_handle)

            self.DevInfoClear()
            self.strvDevCtrl.set("打开")
            self.cmbDevType["state"] = "readonly"
            self.cmbDevIdx["state"] = "readonly"
            self._isOpen = False
        else:
            self._cur_dev_info = self._dev_info[self.cmbDevType.get()]

            #Open Device
            self._dev_handle = self._zcan.OpenDevice(self._cur_dev_info["dev_type"], 
                                                     self.cmbDevIdx.current(), 0)
            if self._dev_handle == INVALID_DEVICE_HANDLE:
                #Open failed
                messagebox.showerror(title="打开设备", message="打开设备失败！")
                return 
            
            #Update Device Info Display
            self.DevInfoRead()

            self._is_canfd = self._cur_dev_info["chn_info"]["is_canfd"]
            self._res_support = self._cur_dev_info["chn_info"]["sf_res"]

            self.strvDevCtrl.set("关闭")
            self.cmbDevType["state"] = tk.DISABLED
            self.cmbDevIdx["state"] = tk.DISABLED
            self._isOpen = True 
        self.ChnInfoUpdate(self._isOpen)
        self.ChnInfoDisplay(self._isOpen)

    def BtnOpenCAN_Click(self):
        if self._isChnOpen:
            #wait read_thread exit
            if USE_THREAD:
                self._terminated = True
                self._read_thread.join(0.1)

            #stop send thread
            self._terminated = True
            self._send_thread.join(0.1)

            #Close channel
            self._zcan.ResetCAN(self._can_handle)
            self.strvCANCtrl.set("打开")
            self._isChnOpen = False
            self.buttonSS["state"] = tk.DISABLED

        else:
            #Initial channel
            if self._res_support: #resistance enable
                ip = self._zcan.GetIProperty(self._dev_handle)
                self._zcan.SetValue(ip, 
                                    str(self.cmbCANChn.current()) + "/initenal_resistance", 
                                    '1' if self.cmbResEnable.current() == 0 else '0')
                self._zcan.ReleaseIProperty(ip)

            #set usbcan-e-u baudrate
            if self._cur_dev_info["dev_type"] in USBCAN_XE_U_TYPE:
                ip = self._zcan.GetIProperty(self._dev_handle)
                self._zcan.SetValue(ip, 
                                    str(self.cmbCANChn.current()) + "/baud_rate", 
                                    self._cur_dev_info["chn_info"]["baudrate"][self.cmbBaudrate.get()])
                self._zcan.ReleaseIProperty(ip)

            #set usbcanfd clock 
            if self._cur_dev_info["dev_type"] in USBCANFD_TYPE:
                ip = self._zcan.GetIProperty(self._dev_handle)
                self._zcan.SetValue(ip, str(self.cmbCANChn.current()) + "/clock", "60000000")
                self._zcan.ReleaseIProperty(ip)
            
            chn_cfg = ZCAN_CHANNEL_INIT_CONFIG()
            chn_cfg.can_type = ZCAN_TYPE_CANFD if self._is_canfd else ZCAN_TYPE_CAN
            if self._is_canfd:
                chn_cfg.config.canfd.mode = self.cmbCANMode.current()
                chn_cfg.config.canfd.abit_timing = self._cur_dev_info["chn_info"]["baudrate"][self.cmbBaudrate.get()]
                chn_cfg.config.canfd.dbit_timing = self._cur_dev_info["chn_info"]["data_baudrate"][self.cmbDataBaudrate.get()]
            else:
                chn_cfg.config.can.mode = self.cmbCANMode.current()
                if self._cur_dev_info["dev_type"] in USBCAN_I_II_TYPE:
                    brt = self._cur_dev_info["chn_info"]["baudrate"][self.cmbBaudrate.get()]
                    chn_cfg.config.can.timing0 = brt["timing0"] 
                    chn_cfg.config.can.timing1 = brt["timing1"]
                    chn_cfg.config.can.acc_code = 0
                    chn_cfg.config.can.acc_mask = 0xFFFFFFFF

            self._can_handle = self._zcan.InitCAN(self._dev_handle, self.cmbCANChn.current(), chn_cfg)
            if self._can_handle == INVALID_CHANNEL_HANDLE:
                messagebox.showerror(title="打开通道", message="初始化通道失败!")
                return 

            ret = self._zcan.StartCAN(self._can_handle)
            if ret != ZCAN_STATUS_OK: 
                messagebox.showerror(title="打开通道", message="打开通道失败!")
                return 

            #start transmit threading
            self._terminated = False
            self._send_thread = threading.Thread(target=self.WriteCAN, daemon=True)
            self._send_thread.start()

            #start receive thread
            if USE_THREAD:
                self._terminated = False
                self._read_thread = threading.Thread(target=self.MsgReadThreadFunc, daemon=True)
                self._read_thread.start()

            self.strvCANCtrl.set("关闭")
            self._isChnOpen = True 
            self.buttonSS["state"] = tk.NORMAL

        self.ChnInfoDisplay(not self._isChnOpen)

    def BtnClrCnt_Click(self):
        self._tx_cnt = 0
        self._rx_cnt = 0
        self._view_cnt = 0
        self.strvTxCnt.set("0")
        self.strvRxCnt.set("0")

        self.OutputText.delete("1.0",tk.END)

    def WriteCAN(self):
        try:
            while not self._terminated:       
                if ((self._is_sending) and (self.fileName != "")):
                    # 固定为can帧
                    is_canfd_msg = False
                    if is_canfd_msg:
                        msg = ZCAN_TransmitFD_Data()
                    else:
                        msg = ZCAN_Transmit_Data()
                    # "正常发送"
                    msg.transmit_type = 0
                    try:
                        msg.frame.can_id = DOWN_ID
                    except:
                        msg.frame.can_id = 0
                    # "数据帧"   
                    msg.frame.rtr = 0
                    # "标准帧"
                    msg.frame.eff = 0

                    if not is_canfd_msg:
                        msg.frame.can_dlc = 8
                        msg_len = msg.frame.can_dlc
                    else:
                        msg.frame.brs = 1 if self.cmbMsgCANFD.current() == 2 else 0
                        msg.frame.len = self.__dlc2len(self.cmbMsgLen.current())
                        msg_len = msg.frame.len


                    data = ("FF FF FF FF FF FF FF FF").split(' ')
                    for i in range(msg_len):
                        if i < len(data):
                            try:
                                msg.frame.data[i] = int(data[i], 16)
                            except:
                                msg.frame.data[i] = 0
                        else:
                            msg.frame.data[i] = 0
                    # 发送帧数
                    msg_num = 1
                    # 发送次数,多次发送功能未实现
                    msg_cnt = 1
                    # 发送间隔(ms)
                    period  = 1
                    # ID递增
                    id_is_add = False

                    self.OutputText.insert(tk.END,"Request MCU to receive the file !\r\n")
                    for i in range(100):
                        self.MsgSend(msg, is_canfd_msg, msg_num, msg_cnt, period, id_is_add)
                        time.sleep(0.005)
                        if not USE_THREAD:
                            self.MsgReadFunc()
                        if(self.mcu_Status == UP_READY_STATUS): 
                            break
                    if(self.mcu_Status == UP_READY_STATUS):
                        self.OutputText.insert(tk.END,"Start sending the file !\r\n")
                        self.progressbarSend['maximum']=len(self.str_appFile)
                        for appFile_line in range(len(self.str_appFile)):
                            self.progressbarSend["value"] = appFile_line + 1
                            self.OutputText.insert(tk.END,"The data of line "+str(appFile_line+1)+ " was sent!\r\n")
                            # dispaly update
                            self.OutputText.yview_moveto(1)
                            strToSend = self.str_appFile[appFile_line].strip()
                            listToSend = list(strToSend)
                            NumOfFrame = len(listToSend)//msg_len
                            LenOfLastFrame = len(listToSend)%msg_len
                            NumOfSend = 0
                            for j in range(NumOfFrame):
                                for i in range(msg_len):
                                    msg.frame.data[i] = ord(listToSend[NumOfSend])
                                    logging.debug('No %d row, No %d column, No %d frame, data[%d] is 0x%x', appFile_line, NumOfSend, j, i, msg.frame.data[i])
                                    NumOfSend += 1
                                while True:
                                    logging.debug('In while 1:send first 8N data of line')
                                    if not USE_THREAD:
                                        self.MsgReadFunc()
                                    if(self.mcu_Status == UP_READY_STATUS): 
                                        self.mcu_Status = UP_ERR_Str                     
                                        self.MsgSend(msg, is_canfd_msg, msg_num, msg_cnt, period, id_is_add)
                                        break

                            msg.frame.can_dlc = LenOfLastFrame
                            for i in range(LenOfLastFrame):
                                msg.frame.data[i] = ord(listToSend[NumOfSend])
                                logging.debug('No %d row, No %d column, No %d frame, data[%d] is 0x%x', appFile_line, NumOfSend, j+1, i, msg.frame.data[i])
                                NumOfSend += 1
                            while True:
                                logging.debug('In while 2:send rest data of line')
                                if not USE_THREAD:
                                    self.MsgReadFunc()
                                if(self.mcu_Status == UP_READY_STATUS):  
                                    self.mcu_Status = UP_ERR_Str                     
                                    self.MsgSend(msg, is_canfd_msg, msg_num, msg_cnt, period, id_is_add)
                                    break
                            # one line of file was send    
                            if(appFile_line == (len(self.str_appFile)-1)):
                                msg.frame.data[0] =  DOWN_FILE_END_CMD
                            else:
                                msg.frame.data[0] =  DOWN_LINE_END_CMD
                            while True: 
                                # last line of S19 is inactive,so don't send DOWN_LINE_END_CMD to programing
                                if msg.frame.data[0] ==  DOWN_FILE_END_CMD:
                                    logging.debug('In while 3:send cmd of DOWN_FILE_END')
                                elif msg.frame.data[0] ==  DOWN_LINE_END_CMD:
                                    logging.debug('In while 3:send cmd of DOWN_LINE_END')
                                if not USE_THREAD:
                                    self.MsgReadFunc()
                                if(self.mcu_Status == UP_READY_STATUS): 
                                    self.mcu_Status = UP_ERR_Str                      
                                    self.MsgSend(msg, is_canfd_msg, msg_num, msg_cnt, period, id_is_add)
                                    break                  
                            msg.frame.can_dlc = 8
                        # all line of file was send
                        self.OutputText.insert(tk.END,"The file was sent successfully !\r\n")

                        self.strvSend.set("Start")
                        self._is_sending = False
                        self.buttonSS["state"] = tk.NORMAL
                    else:
                        self.OutputText.insert(tk.END,"MCU have not respond !\r\n")

                        self.strvSend.set("Start")
                        self._is_sending = False
                        self.buttonSS["state"] = tk.NORMAL

                # The can channel was open but the file has not be selected
                elif((self._is_sending) and (self.fileName == "")):
                    messagebox.showinfo(title='Warning', message="You have not selected any files!")
                    self.strvSend.set("Start")
                    self._is_sending = False
                    self.buttonSS["state"] = tk.NORMAL

        except:
            self.OutputText.insert(tk.END,"Stop sending the file !\r\n")


    def processButtonSS(self):
        if not self._is_sending:
            self._is_sending = True
            self.buttonSS["state"] = tk.DISABLED
            self.strvSend.set("Sending")
            
    def processButtonSelect(self):
        self.fileName = filedialog.askopenfilename(filetypes = [("S19","*.s19")])
        if self.fileName != "":
            self.labelFile.config(text = self.fileName)
            appFile = open(self.fileName, mode="r")
            self.str_appFile = appFile.readlines()
            appFile.close()
        else:
            self.labelFile.config(text = "You have not selected any files!")

if __name__ == "__main__":
    demo = ZCAN_Demo()
    demo.mainloop()